/*
Copyright 2008, 2009 Joonas Lehtolahti

This file is part of Openfire-C.

Openfire-C is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Openfire-C is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Openfire-C.  If not, see <http://www.gnu.org/licenses/>.
*/

/* Xfire packet handling functions */

#include "XfirePacket.h"
#include <string.h>
#include <stdio.h>

/* Private functions: */

unsigned char *XfirePacketParseAttribute (void **attr, int aid, unsigned char *crawler, int dataleft);
unsigned int XfireStreamPacket(struct XfirePacket *, unsigned char *);
unsigned int XfireStreamAttr(void *, int, unsigned char *);

/*
 * XfirePacketFree (struct XfirePacket *obj)
 *   Frees memory taken by allocated XfirePacket struct
 *   including all allocated memory inside it
 * 
 * Parameters:
 *   obj   pointer to the data area to be freed
 * 
 * Returns:
 *   nothing
 */
DLLEXPORT(void) XfirePacketFree(struct XfirePacket *obj) {
	unsigned int attr_index;
	if (obj) {
		for (attr_index=0;attr_index<obj->attrs;attr_index++) {
			XfirePattrFree_internal(obj->attributes[attr_index]);
		}
		free(obj->attributes);
		free(obj);
	}
}

/*
 * XfirePattr4Free (struct XfirePattr4 *obj)
 *   Frees memory taken by allocated XfirePattr4 struct
 *   including all allocated memory inside it
 * 
 * Parameters:
 *   obj   pointer to the data area to be freed
 * 
 * Returns:
 *   nothing
 */
DLLEXPORT(void) XfirePattr4Free(struct XfirePattr4 *obj) {
	unsigned int index;
	if (obj) {
		switch (obj->aid) {
			case XFP_TYPE_STRING:
			case XFP_TYPE_SID:
			case XFP_TYPE_GCID:
				for (index=0; index < obj->size; index++)
					n0sfree((non0string*)(obj->attr[index]));
				break;
			case XFP_TYPE_INT:
			case XFP_TYPE_LONG:
			case XFP_TYPE_BOOLEAN:
				for (index=0; index < obj->size; index++)
					free((obj->attr)[index]);
				break;
			case XFP_TYPE_LIST:
				for (index=0; index < obj->size; index++)
					XfirePattr4Free(obj->attr[index]);
				break;
			case XFP_TYPE_PACKET:
			case XFP_TYPE_NPACKET:
				for (index=0; index < obj->size; index++)
					XfirePacketFree(obj->attr[index]);
				break;
		}
		free(obj->attr);
		free(obj);
	}
}

#ifndef XfirePattrFree_internal
DLLEXPORT(void) XfirePattrFree(struct XfirePattr *obj) {
	XfirePattrFree_internal(obj);
}
#endif

/*
 * XfirePattrFree_internal (struct XfirePattr *obj)
 *   Frees memory taken by allocated XfirePattr struct
 *   including all allocated memory inside it
 * 
 * Parameters:
 *   obj   pointer to the data area to be freed
 * 
 * Returns:
 *   nothing
 */
void XfirePattrFree_internal(struct XfirePattr *obj) {
	if (obj) {
		n0sfree(obj->name);
		switch (obj->aid) {
			case XFP_TYPE_STRING:
			case XFP_TYPE_SID:
			case XFP_TYPE_GCID:
				n0sfree((non0string*)obj->attr);
				break;
			case XFP_TYPE_INT:
			case XFP_TYPE_LONG:
			case XFP_TYPE_BOOLEAN:
				free(obj->attr);
				break;
			case XFP_TYPE_LIST:
				XfirePattr4Free(obj->attr);
				break;
			case XFP_TYPE_PACKET:
			case XFP_TYPE_NPACKET:
				XfirePacketFree(obj->attr);
				break;
		}
		free(obj);
	}
}

/**
 * XfirePacketParse (non0string *data)
 *   Parses the network data and creates corresponding data structures
 * 
 * Parameters:
 *   non0string *data   a pointer to the non0string struct holding the
 *                      stream data
 * 
 * Returns:
 *   A pointer to XfirePacket struct that holds the parsed information
 *   NULL, if parsing failed
 * 
 * Notes:
 *   The calling function must free the returned data after use with
 *   function XfirePacketFree()
 */
DLLEXPORT(struct XfirePacket*) XfirePacketParse(non0string *data) {
	struct XfirePacket *xp;
	struct XfirePattr  *xpa;
	unsigned int intvalue;
	unsigned char *crawler;
	unsigned int attr_index;
	
	if (data->l < 5) {
		/* Impossibly short packet */
		/* fprintf(stderr, "Impossibly short packet.\n"); */
		return NULL;
	}
	
	crawler = data->s;
	intvalue = *(unsigned int*)crawler & XFP_MASK_PACKETLENGTH;
	crawler += XFP_SIZE_PACKETLENGTH;
	if (intvalue != data->l) {
		/* Packet length does not match the data length */
		/* fprintf(stderr, "Packet length does not match the data length.\n"); */
		return NULL;
	}
	
	xp = (struct XfirePacket *)malloc(sizeof(struct XfirePacket));
	
	xp->length = intvalue;
	
	xp->pid = *(unsigned int*)crawler & XFP_MASK_PACKETID;
	crawler += XFP_SIZE_PACKETID;
	
	xp->attrs = *(unsigned int*)crawler & XFP_MASK_ATTRIBUTECOUNT;
	crawler += XFP_SIZE_ATTRIBUTECOUNT;
	
	if (xp->attrs == 0) {
		/* No attributes, packet parsing complete */
		xp->attributes = NULL;
		return xp;
	}
	
	/* There are one or more attributes, parse them now */
	
	if (*(crawler+XFP_SIZE_ATTRIBUTENAMELENGTH) < 32) {
		xp->shortnames = 1;
	}
	else {
		xp->shortnames = 0;
	}
	
	xp->attributes = (struct XfirePattr **)malloc(sizeof(struct XfirePattr*)*xp->attrs);
	
	attr_index = 0;
	while (attr_index < xp->attrs) {
		if ((unsigned int)(crawler-(unsigned char*)data->s) >= data->l) {
			/* not enough data! */
			break;
		}
		
		/* Create new attribute object */
		xpa = xp->attributes[attr_index] = (struct XfirePattr *)malloc(sizeof(struct XfirePattr));
		/* Get attribute name */
		if (xp->shortnames) {
			xpa->name = n0salloc(XFP_SIZE_SHORTATTRIBUTENAME);
			memcpy(xpa->name->s, crawler, XFP_SIZE_SHORTATTRIBUTENAME);
			crawler++;
		}
		else {
			intvalue = *(unsigned int*)crawler & XFP_MASK_ATTRIBUTENAMELENGTH;
			crawler += XFP_SIZE_ATTRIBUTENAMELENGTH;
			xpa->name = n0salloc(intvalue);
			memcpy(xpa->name->s, crawler, intvalue);
			crawler += intvalue;
		}
		/* Get attribute type */
		xpa->aid = *(unsigned int*)crawler & XFP_MASK_ATTRIBUTETYPE;
		crawler += XFP_SIZE_ATTRIBUTETYPE;
		
		crawler = XfirePacketParseAttribute(&(xpa->attr), xpa->aid, crawler, xp->length - (crawler - data->s));
		
		attr_index++;
	}
	
	if (attr_index < xp->attrs) {
		/* Error occured */
		/* free all allocated memory so far */
		for (intvalue=0;intvalue<attr_index;intvalue++) {
			XfirePattrFree_internal(xp->attributes[intvalue]);
		}
		free(xp->attributes);
		free(xp);
		
		/* fprintf(stderr, "Attribute parsing error.\n"); */
		return NULL;
	}
	/* TODO: Check if crawler - data->s is the same as xp->length
	 * more: error -> XfirePacketFree, return NULL
	 * same: goodie!
	 * less: set xp->length to crawler - data->s
	 **/
	intvalue = crawler - data->s;
	if (intvalue > data->l) {
		/* Read too much data... */
		/* All that work and nothing to show for it */
		XfirePacketFree(xp);
		/* fprintf(stderr, "Read too much data.\n"); */
		return NULL;
	}
	else if (intvalue < data->l) {
		/* All data not read, possibly a fake stream */
		xp->length = intvalue;
	}
	return xp;
}

/*
 * XfirePacketParseAttribute (void *attr, int aid, int *crawler)
 *   Parses attribute data from crawler into xpa
 * 
 * Parameters:
 *   attr      pointer to attribute location pointer
 *   aid       attribute type
 *   crawler   pointer to packet data stream
 *   dataleft  how many bytes there is still data after crawler
 * 
 * Returns:
 *   new position for crawler
 */
unsigned char *XfirePacketParseAttribute (void **attr, int aid, unsigned char *crawler, int dataleft) {
	struct XfirePattr4 *xpal;
	unsigned int intvalue;
	non0string *fakedata;
	
	switch (aid) {
		case XFP_TYPE_INT:
			*attr = malloc(sizeof(int));
			*((int*)*attr) = *(unsigned int*)crawler & XFP_MASK_ATTRINTVALUE;
			crawler += XFP_SIZE_ATTRINTVALUE;
			break;
		case XFP_TYPE_BOOLEAN:
			*attr = malloc(sizeof(int));
			*((int*)*attr) = *(unsigned int*)crawler & XFP_MASK_ATTRBOOLEANVALUE;
			crawler += XFP_SIZE_ATTRBOOLEANVALUE;
			break;
		case XFP_TYPE_LONG:
			*attr = malloc(sizeof(unsigned long long int));
			*((unsigned long long int*)*attr) = *(unsigned long long int*)crawler & XFP_MASK_ATTRLONGVALUE;
			crawler += XFP_SIZE_ATTRLONGVALUE;
			break;
		case XFP_TYPE_STRING:
			intvalue = *(unsigned int*)crawler & XFP_MASK_ATTRSTRINGLENGTH;
			crawler += XFP_SIZE_ATTRSTRINGLENGTH;
			*attr = n0salloc(intvalue);
			memcpy(((non0string*)*attr)->s, crawler, intvalue);
			crawler += intvalue;
			break;
		case XFP_TYPE_SID:
			*attr = n0salloc(XFP_SIZE_ATTRSIDVALUE);
			memcpy(((non0string*)*attr)->s, crawler, XFP_SIZE_ATTRSIDVALUE);
			crawler += XFP_SIZE_ATTRSIDVALUE;
			break;
		case XFP_TYPE_GCID:
			*attr = n0salloc(XFP_SIZE_ATTRGCIDVALUE);
			memcpy(((non0string*)*attr)->s, crawler, XFP_SIZE_ATTRGCIDVALUE);
			crawler += XFP_SIZE_ATTRGCIDVALUE;
			break;
		case XFP_TYPE_LIST:
			*attr = xpal = (struct XfirePattr4 *)malloc(sizeof(struct XfirePattr4));
			
			xpal->aid = *(unsigned int*)crawler & XFP_MASK_ATTRIBUTETYPE;
			crawler += XFP_SIZE_ATTRIBUTETYPE;
			
			xpal->size = *(unsigned int*)crawler & XFP_MASK_ATTRLISTLENGTH;
			crawler += XFP_SIZE_ATTRLISTLENGTH;
			
			xpal->attr = (void**)malloc(xpal->size*sizeof(void*));
			for (intvalue=0; intvalue<xpal->size; intvalue++) {
				crawler = XfirePacketParseAttribute(&(xpal->attr[intvalue]), xpal->aid, crawler, dataleft);
			}
			break;
		case XFP_TYPE_PACKET:
		case XFP_TYPE_NPACKET:
			intvalue = XFP_SIZE_PACKETLENGTH + XFP_SIZE_PACKETID;
			fakedata = n0salloc(dataleft+intvalue);
			*((unsigned int*)fakedata->s) = fakedata->l;
			memcpy(fakedata->s+intvalue, crawler, fakedata->l-intvalue);
			*attr = XfirePacketParse(fakedata);
			n0sfree(fakedata);
			crawler += ((struct XfirePacket *)*attr)->length-intvalue;
			break;
	}
	
	return crawler;
}

/*
 * XfirePacketCreate (uint attributes, int shortnames)
 *   Allocates memory for a new XfirePacket struct
 * 
 * Parameters:
 *   attributes  the number of attributes this packet will contain
 *   shortnames  0 or 1 specifying the attribute naming convention
 * 
 * Returns:
 *   pointer to the allocated XfirePacket struct
 *   NULL, if failed
 * 
 * Notes:
 *   the calling function must free the packet later with XfirePacketFree function
 */
DLLEXPORT(struct XfirePacket*) XfirePacketCreate(unsigned int attributes, int shortnames) {
	struct XfirePacket *p = (struct XfirePacket *)malloc(sizeof(struct XfirePacket));
	if (!p) return NULL;
	p->attributes = (struct XfirePattr**)malloc(attributes*sizeof(struct XfirePattr*));
	if (!p->attributes) {
		free(p);
		return NULL;
	}
	p->length = 0;
	p->pid = 0;
	p->attrs = attributes;
	p->shortnames = shortnames;
	return p;
}

/*
 * XfirePacketGetPID (non0string *data)
 *   Quickly retrieves the packet ID number from stream without parsing the whole packet
 * 
 * Parameters:
 *   data   pointer to the data
 * 
 * Returns:
 *   packet id number
 *   0, if the data was not correct
 */
DLLEXPORT(unsigned int) XfirePacketGetPID(non0string *data) {
	if (data) {
		if (data->l >= XFP_SIZE_HEADER) {
			return *(unsigned int*)(data->s+XFP_SIZE_PACKETLENGTH) & XFP_MASK_PACKETID;
		}
	}
	return 0;
}

/*
 * XfireStreamFromPacket (struct XfirePacket *packet)
 *   Generates data ready to be sent to network stream
 *   from XfirePacket structure
 * 
 * Parameters:
 *   packet   pointer to struct XfirePacket
 * 
 * Returns:
 *   pointer to non0string struct holding the data
 *   NULL, if packet was NULL
 */
DLLEXPORT(non0string*) XfireStreamFromPacket(struct XfirePacket *packet) {
	unsigned char *buffer;
	unsigned char *helper;
	non0string *tbr;
	
	unsigned int headerlength = XFP_SIZE_PACKETLENGTH + XFP_SIZE_PACKETID;
	
	if (!packet) return NULL;
	
	buffer = (unsigned char*)malloc((XFP_PACKET_MAXSIZE+1)*sizeof(unsigned char));
	
	packet->length = headerlength;
	
	packet->length += XfireStreamPacket(packet, buffer+headerlength);
	
	helper = (unsigned char*)(&packet->length);
	memcpy(buffer, helper, XFP_SIZE_PACKETLENGTH);
	helper = (unsigned char*)(&packet->pid);
	memcpy(buffer + XFP_SIZE_PACKETLENGTH, helper, XFP_SIZE_PACKETID);
	
	tbr = n0salloc(packet->length);
	memcpy(tbr->s, buffer, tbr->l);
	free(buffer);
	
	return tbr;
}

/*
 * XfireStreamPacket (struct XfirePacket *packet, unsigned char *buffer)
 *   Internal function, generates packet data to be included in the
 *   master XfireStreamFromPacket data
 * 
 * Parameters:
 *   packet   pointer to struct XfirePacket
 *   buffer   pointer to allocated char space where the data is to be written
 * 
 * Returns:
 *   unsigned integer representing bytes written to the buffer
 */
unsigned int XfireStreamPacket(struct XfirePacket *packet, unsigned char *buffer) {
	unsigned int attr_index;
	unsigned char *helper;
	unsigned char *startlocation = buffer;
	struct XfirePattr *attr;

	helper = (unsigned char*)(&packet->attrs);
	memcpy(buffer, helper, XFP_SIZE_ATTRIBUTECOUNT);
	
	buffer += XFP_SIZE_ATTRIBUTECOUNT;
	for (attr_index = 0; attr_index < packet->attrs; attr_index++) {
		attr = packet->attributes[attr_index];
		if (packet->shortnames) {
			memcpy(buffer, attr->name->s, XFP_SIZE_SHORTATTRIBUTENAME);
			buffer += XFP_SIZE_SHORTATTRIBUTENAME;
		}
		else {
			helper = (unsigned char*)(&attr->name->l);
			memcpy(buffer, helper, XFP_SIZE_ATTRIBUTENAMELENGTH);
			buffer += XFP_SIZE_ATTRIBUTENAMELENGTH;
			
			memcpy(buffer, attr->name->s, attr->name->l);
			buffer += attr->name->l;
		}
		
		helper = (unsigned char*)(&attr->aid);
		memcpy(buffer, helper, XFP_SIZE_ATTRIBUTETYPE);
		buffer += XFP_SIZE_ATTRIBUTETYPE;
		buffer += XfireStreamAttr(attr->attr, attr->aid, buffer);
	}
	return buffer-startlocation;
}

/*
 * XfireStreamAttr (void *attr, int aid, unsigned char *buffer)
 *   Internal function, generates attribute data to be included in the
 *   master XfireStreamFromPacket data
 * 
 * Parameters:
 *   attr     pointer to attribute data
 *   aid      attribute type
 *   buffer   pointer to allocated char space where the data is to be written
 * 
 * Returns:
 *   unsigned integer representing bytes written to the buffer
 */
unsigned int XfireStreamAttr(void *attr, int aid, unsigned char *buffer) {
	unsigned int i;
	unsigned char *helper;
	unsigned char *startlocation = buffer;
	struct XfirePattr4 * lister;
	non0string *nos;
	switch (aid) {
		case XFP_TYPE_INT:
			helper = (unsigned char*)attr;
			memcpy(buffer, helper, XFP_SIZE_ATTRINTVALUE);
			buffer += XFP_SIZE_ATTRINTVALUE;
			break;
			
		case XFP_TYPE_BOOLEAN:
			helper = (unsigned char*)attr;
			memcpy(buffer, helper, XFP_SIZE_ATTRBOOLEANVALUE);
			buffer += XFP_SIZE_ATTRBOOLEANVALUE;
			break;
			
		case XFP_TYPE_LONG:
			helper = (unsigned char*)attr;
			memcpy(buffer, helper, XFP_SIZE_ATTRLONGVALUE);
			buffer += XFP_SIZE_ATTRLONGVALUE;
			break;
			
		case XFP_TYPE_STRING:
			nos = (non0string*)attr;
			helper = (unsigned char*)(&nos->l);
			memcpy(buffer, helper, XFP_SIZE_ATTRSTRINGLENGTH);
			buffer += XFP_SIZE_ATTRSTRINGLENGTH;
			
			memcpy(buffer, nos->s, nos->l);
			buffer += nos->l;
			break;
		
		case XFP_TYPE_SID:
		case XFP_TYPE_GCID:
			nos = (non0string*)attr;
			
			memcpy(buffer, nos->s, nos->l);
			buffer += nos->l;
			break;
			
		case XFP_TYPE_LIST:
			lister = (struct XfirePattr4 *)attr;
			
			helper = (unsigned char*)(&lister->aid);
			memcpy(buffer, helper, XFP_SIZE_ATTRIBUTETYPE);
			buffer += XFP_SIZE_ATTRIBUTETYPE;
			
			helper = (unsigned char*)(&lister->size);
			memcpy(buffer, helper, XFP_SIZE_ATTRLISTLENGTH);
			buffer += XFP_SIZE_ATTRLISTLENGTH;
			
			for (i=0; i<lister->size; i++) {
				buffer += XfireStreamAttr(lister->attr[i], lister->aid, buffer);
			}
			
			break;
			
		case XFP_TYPE_PACKET:
		case XFP_TYPE_NPACKET:
			buffer += XfireStreamPacket((struct XfirePacket *)attr, buffer);
			
			break;
	}
	
	return buffer-startlocation;
}
